package it.polito.connectedcar.tcc.zone;

import it.polito.appeal.traci.TrafficLight;
import it.polito.connectedcar.tcc.TCCVehicleAbstraction;

import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * A TCCZone is an area in the space managed by the TCC. A zone is characterized
 * by a Token code which uniquely identify the zone from the others. A zone is
 * composed by quadrants; some of these quadrants are called transit quadrants
 * because they are on the borders of the area managed by this token. <br/>
 * <br/>
 * Area definition: ( red quadrants are the transit quadrants )
 * <table>
 * <tbody style="border-color:#000000;border-style:solid;border-width:thin">
 * <tr>
 * <td><b style="color:red">0</b></td>
 * <td><b style="color:red">1</b></td>
 * <td><b style="color:red">2</></td>
 * <td><b style="color:red">3</></td>
 * <td><b style="color:red">4</></td>
 * </tr>
 * <tr>
 * <td><b style="color:red">5</b></td>
 * <td><b>6</b></td>
 * <td><b >7</></td>
 * <td><b >8</></td>
 * <td><b style="color:red">9</></td>
 * </tr>
 * <tr>
 * <td><b style="color:red">10</b></td>
 * <td><b >11</b></td>
 * <td><b >12</></td>
 * <td><b >13</></td>
 * <td><b style="color:red">14</></td>
 * </tr>
 * <tr>
 * <td><b style="color:red">15</b></td>
 * <td><b>16</b></td>
 * <td><b >17</></td>
 * <td><b >18</></td>
 * <td><b style="color:red">19</></td>
 * </tr>
 * <tr>
 * <td><b style="color:red">20</b></td>
 * <td><b style="color:red">21</b></td>
 * <td><b style="color:red">22</></td>
 * <td><b style="color:red">23</></td>
 * <td><b style="color:red">24</></td>
 * </tr>
 * </tbody>
 * </table>
 * The zone contains a reference of all the vehicles present in its quadrants
 * together with the Information about traffic Lights present in each quadrant.
 * 
 * At each time a vehicle either belongs to a quadrant or is outside the zone's
 * area and is going toward some other zone. A Zone maintains a Set of
 * TokenLinks, each of them representing the next-hop token in a certain
 * direction , starting from the current Token. the TokenLinks are used to
 * communicate to vehicles in transit quadrants which are the next possible
 * Token-Zone they have to consider.
 * 
 * @author Nicola Aresta
 * 
 */
public abstract class TCCZone {

	private Token t;
	private int transitQuadrantsLevelCount;
	private int quadrantsRows;
	private int quadrantsColumns;
	private Point2D bottomRightsCoordinates;
	private Point2D topLeftCoordinates;

	/*
	 * The collections of elements has to be inserted into the program
	 */

	private Map<Integer, CopyOnWriteArraySet<TCCVehicleAbstraction>> quadrantsVehiclesMap;

	private Map<String, Integer> cachedVehiclesQuadrants;

	private Map<Integer, List<TrafficLight>> quadrantsTrafficLightsMap;

	protected TokenLinkList tokenLinkList;

	public TCCZone(Token t, Point2D topLeftCoordinates,
			Point2D bottomRightCoordinates, int quadrantsRows,
			int quadrantsColumns, int transitQuadrantsLevel) {
		this.t = t;
		this.topLeftCoordinates = topLeftCoordinates;
		this.bottomRightsCoordinates = bottomRightCoordinates;
		this.quadrantsColumns = quadrantsColumns;
		this.quadrantsRows = quadrantsRows;
		this.transitQuadrantsLevelCount = transitQuadrantsLevel;

		quadrantsVehiclesMap = new ConcurrentHashMap<Integer, CopyOnWriteArraySet<TCCVehicleAbstraction>>();
		cachedVehiclesQuadrants = new ConcurrentHashMap<String, Integer>();
		for (int i = 0; i < quadrantsRows * quadrantsColumns; i++) {
			quadrantsVehiclesMap.put(i,
					new CopyOnWriteArraySet<TCCVehicleAbstraction>());
		}
	}

	/**
	 * giving a couple of coordinates , it returns
	 * 
	 * @param utm_x
	 *            the vehicle X coordinate in the space
	 * @param utm_y
	 *            the vehicle Y coordinate in the space
	 * @return index >0 if the coordinates belongs to a quadrant, index = -1
	 *         otherwise
	 */
	public final int getQuadrantIndex(double utm_x, double utm_y) {
		int quadrantsColumns = getQuadrantsColumns();
		int quadrantsRows = getQuadrantsRows();

		Point2D topLeftCoord = getTopLeftZoneCoordinates();
		Point2D bottomRightCoord = getBottomRightZoneCoordinates();

		double width = bottomRightCoord.getX() - topLeftCoord.getX();
		double height = topLeftCoord.getY() - bottomRightCoord.getY();

		if (utm_x < topLeftCoord.getX() || utm_x > bottomRightCoord.getX()
				|| utm_y < bottomRightCoord.getY()
				|| utm_y > topLeftCoord.getY())
			return -1;

		double absoluteX = Math.abs(utm_x - topLeftCoord.getX());
		double absoluteY = Math.abs(utm_y - topLeftCoord.getY());

		int xIndex = (int) Math.floor(absoluteX / (width / quadrantsColumns));
		int yIndex = (int) Math.floor(absoluteY / (height / quadrantsRows));

		int index = xIndex;
		for (int i = 0; i < yIndex; i++)
			index += (quadrantsRows);

		return index;
	}

	/**
	 * discover if @param quadrantIndex is a normal sub-quadrant or not , inside
	 * the managing zone
	 * 
	 * @param quadrantIndex
	 * @return <b>TRUE</b> if the quadrant index belongs to the innermost
	 *         quadrants (non transit)<br/>
	 *         <b>FALSE</b> otherwise
	 */
	public boolean isSubQuadrant(int quadrantIndex) {
		int transientQuadrantsLevelCount = getTransientQudrantsLevelCount();
		int row = (quadrantIndex) / getQuadrantsColumns();
		if (row < transientQuadrantsLevelCount
				|| row >= getQuadrantsRows() - transientQuadrantsLevelCount)
			return false;

		int column = (quadrantIndex) % getQuadrantsColumns();
		if (column < transientQuadrantsLevelCount
				|| column >= getQuadrantsColumns()
						- transientQuadrantsLevelCount)
			return false;

		// in this case it is a subzone
		return true;
	}

	/**
	 * discover if @param quadrantIndex is atransit quadrant for the managing
	 * zone
	 * 
	 * @param quadrantIndex
	 * @return
	 */
	public boolean isTransitQuadrant(int quadrantIndex) {
		return !isSubQuadrant(quadrantIndex);
	}

	/**
	 * Given a quadrant's index , returns all the neighbors quadrants a vehicle
	 * needs to take into account. The algorithm to extract the quadrants take
	 * into consideration all the quadrants surrounding the current quadrant
	 * index
	 * 
	 * @param quadrantsIndex
	 * @return
	 */
	public Set<Integer> getInterestingQuadrants(int quadrantsIndex) {
		Set<Integer> indexes = new HashSet<Integer>();
		indexes.add(quadrantsIndex);

		int indexRow = (int) Math.floor(quadrantsIndex / getQuadrantsColumns());
		int indexColumn = quadrantsIndex % getQuadrantsColumns();

		if (indexColumn > 0)
			indexes.add(quadrantsIndex - 1);
		if (indexColumn < (getQuadrantsColumns() - 1))
			indexes.add(quadrantsIndex + 1);

		if (indexRow > 0) {
			int upRowIndex = quadrantsIndex - getQuadrantsColumns();
			if (indexColumn > 0)
				indexes.add(upRowIndex - 1);
			if (indexColumn < (getQuadrantsColumns() - 1))
				indexes.add(upRowIndex + 1);
		}

		if (indexRow < (getQuadrantsRows() - 1)) {
			int downRowIndex = quadrantsIndex + getQuadrantsColumns();
			if (indexColumn > 0)
				indexes.add(downRowIndex - 1);
			if (indexColumn < (getQuadrantsColumns() - 1))
				indexes.add(downRowIndex + 1);
		}
		return indexes;
	}

	/**
	 * remove the named vehicle from all the collections this zone is managing ,
	 * even caches and Token Links's transit lists
	 * 
	 * @param tva
	 */
	public void removeVehicle(TCCVehicleAbstraction tva) {
		TokenLink link = tokenLinkList.getTokenLink(tva.getApproachingToken());
		if (link != null) {
			for (Integer quadrant : quadrantsVehiclesMap.keySet()) {
				Set<TCCVehicleAbstraction> vehicles = quadrantsVehiclesMap
						.get(quadrant);
				if (vehicles.remove(tva)) {
					cachedVehiclesQuadrants.remove(tva.getVehicleId());
					tokenLinkList.remove(tva);
				}
			}
		}
	}

	/**
	 * add the named vehicle to the quadrant managed by this zone, possibly
	 * removing it from the previous quadrant. This method will update cache
	 * values
	 * 
	 * @param tva
	 * @param new_quadrant
	 */
	public void addVehicleToQuadrant(TCCVehicleAbstraction tva, int new_quadrant) {
		Integer prev_quadrant = cachedVehiclesQuadrants.get(tva.getVehicleId()
				.toString());

		if (prev_quadrant != null && new_quadrant != prev_quadrant) {
			boolean removedFromLastQuad = quadrantsVehiclesMap.get(
					prev_quadrant).remove(tva);
		}
		if (prev_quadrant == null || new_quadrant != prev_quadrant) {
			tva.setCurrentQuadrant(new_quadrant);

			quadrantsVehiclesMap.get(new_quadrant).add(tva);
			cachedVehiclesQuadrants.put(tva.getVehicleId().toString(),
					new_quadrant);
		}
	}

	/**
	 * In order to compare a zone we use the Token identifying it. If the 2
	 * tokens are equal , the Zones are the same : cannot exists 2 zones having
	 * the same token
	 */
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof Token))
			return false;

		return t.equals(obj);
	}

	/**
	 * return the Token identifying the zone
	 * 
	 * @return
	 */
	public Token getT() {
		return t;
	}

	/**
	 * Returns the number of quadrants to be considered transit,from the
	 * outermost yo the innermost
	 * 
	 * @return
	 */
	public int getTransientQudrantsLevelCount() {
		return transitQuadrantsLevelCount;
	}

	/**
	 * 
	 * @return the number of rows of the Zone's quadrants Map
	 */
	public int getQuadrantsRows() {
		return quadrantsRows;
	}

	/**
	 * 
	 * @return the number of columns of the Zone's quadrants Map
	 */
	public int getQuadrantsColumns() {
		return quadrantsColumns;
	}

	/**
	 * 
	 * @return the top-left point describing the Zone's area
	 */
	public Point2D getTopLeftZoneCoordinates() {
		return topLeftCoordinates;
	}

	/**
	 * 
	 * @return the bottom-right point describing the Zone's area
	 */
	public Point2D getBottomRightZoneCoordinates() {
		return bottomRightsCoordinates;
	}

	/**
	 * The Cache of vehicles in a Zone is represented by a Map in which the key
	 * represents the Token identifier of the vehicle ( its identifier ) , and
	 * the value is the last quadrant index the vehicle was. By retrieving the
	 * quadrant index , the search operation in order to extract a given
	 * vehicle, will be enormously reduced , especially in the case of many
	 * quadrants in a zone and hundreds of vehicles to be managed.
	 * 
	 * @return the cache Map of the vehicles inside the zone.
	 */
	public Map<String, Integer> getCachedVehiclesQuadrants() {
		return cachedVehiclesQuadrants;
	}

	/**
	 * return the TokenLists representing all the Token Zones surrounding by
	 * one-hop the current Zone. Considering the Set of all Zones as a connected
	 * graph G ,and the current Zone as a node N in G, the TokenList is the
	 * subset of G containing all the nodes in G directly reachable from N
	 * 
	 * @return
	 */
	public TokenLinkList getTokenLinkList() {
		return tokenLinkList;
	}

	/**
	 * All the vehicles in the zone are contained in this map . the Key is the
	 * quadrant Index , and the Value id the set of vehicles contained in the
	 * key quadrant index.
	 * 
	 * @return
	 */
	public Map<Integer, CopyOnWriteArraySet<TCCVehicleAbstraction>> getQuadrantsVehiclesMap() {
		return quadrantsVehiclesMap;
	}

	/**
	 * The list of traffic light whose position is inside the given quadrant
	 * index
	 * 
	 * @param quadrantIndex
	 * @return
	 */
	public List<TrafficLight> getTrafficLightsInQuadrants(int quadrantIndex) {
		return quadrantsTrafficLightsMap.get(quadrantIndex);
	}
}
